示例 PRO

import { Button, List, Navigation, NavigationStack, Script, Text, useState } from "scripting"

let engine: AVAudioEngine | null = null
let environment: AVAudioEnvironmentNode | null = null
let players: AVAudioPlayerNode[] = []
let buffers: AVAudioPCMBuffer[] = []
let pending: Promise<void> | null = null
let cancelled = false
let alive = true
async function release() {
  await engine?.stop(); await engine?.dispose(); engine = null
  for (const player of players) await player.dispose(); players = []
  for (const buffer of buffers) await buffer.dispose(); buffers = []
  await environment?.dispose(); environment = null
  await SharedAudioSession.setActive(false)
}
async function stop() { cancelled = true; await engine?.stop(); await pending; await release() }
function Demo() {
  const dismiss = Navigation.useDismiss()
  const [message, setMessage] = useState("Two mono sources: left 330 Hz, right 440 Hz")
  const [busy, setBusy] = useState(false)
  const [running, setRunning] = useState(false)
  const report = (text: string) => { if (alive) setMessage(text) }
  async function move(x: number) {
    try {
      if (!environment) return
      await environment.configure({ listenerPosition: { x, y: 0, z: 0 } })
      const state = await environment.getConfiguration()
      report(`Listener: ${JSON.stringify(state.listenerPosition)}\nLeft source: (-2, 0, -2), 330 Hz\nRight source: (2, 0, -2), 440 Hz`)
    } catch (error) { report(String(error)) }
  }
  async function start() {
    if (pending || engine) return
    cancelled = false; setBusy(true)
    pending = (async () => {
      await SharedAudioSession.setCategory("playback", [])
      await SharedAudioSession.setActive(true)
      engine = await AVAudioEngine.create()
      if (cancelled) return
      environment = await AVAudioEnvironmentNode.create()
      await engine.attach(environment)
      await engine.connect(environment, await engine.getMainMixerNode())
      await environment.configure({ listenerPosition: { x: 0, y: 0, z: 0 }, distanceAttenuation: { model: "inverse", referenceDistance: 1, maximumDistance: 100, rolloffFactor: 1 } })
      const format = new AVAudioFormat({ sampleRate: 48000, channelCount: 1 })
      const config = await environment.getConfiguration()
      const algorithm = config.applicableRenderingAlgorithms.includes(2) ? 2 : config.applicableRenderingAlgorithms[0]
      for (const [x, frequency] of [[-2, 330], [2, 440]]) {
        const player = await AVAudioPlayerNode.create(); players.push(player)
        await engine.attach(player)
        const bus = (await environment.getConfiguration()).nextAvailableInputBus
        await engine.connect(player, environment, { toBus: bus, format })
        await player.setMixing({ position: { x, y: 0, z: -2 }, ...(algorithm === undefined ? {} : { renderingAlgorithm: algorithm }) })
        const data = new Float32Array(24000)
        for (let n = 0; n < data.length; n++) data[n] = Math.sin(2 * Math.PI * frequency * n / 48000) * 0.12
        const buffer = await AVAudioPCMBuffer.fromChannels([data], format); buffers.push(buffer)
        await player.scheduleBuffer(buffer, { loops: true })
      }
      if (cancelled) return
      await engine.start()
      if (cancelled) return
      for (const player of players) { if (cancelled) return; await player.play() }
      if (alive) setRunning(true)
      report(`Running · rendering algorithm ${algorithm ?? "native default"}\nMove the listener with the buttons. Headphones make the comparison easier.`)
    })().catch(async error => { report(String(error)); await release() }).finally(async () => {
      if (cancelled) await release()
      pending = null; if (alive) setBusy(false)
    })
    await pending
  }
  async function stopAndReport() { try { await stop(); if (alive) setRunning(false); report("Stopped") } catch (error) { report(String(error)) } }
  return <NavigationStack><List navigationTitle="3D Audio Scene" toolbar={{ cancellationAction: <Button title="Close" action={dismiss} /> }}>
    <Text>{message}</Text>
    <Button title="Start two sources" action={() => { void start() }} disabled={busy || running} />
    <Button title="Listener left: x = -2" action={() => { void move(-2) }} disabled={!running} />
    <Button title="Listener center: x = 0" action={() => { void move(0) }} disabled={!running} />
    <Button title="Listener right: x = 2" action={() => { void move(2) }} disabled={!running} />
    <Button title="Stop" action={() => { void stopAndReport() }} />
  </List></NavigationStack>
}
async function main() { try { await Navigation.present({ element: <Demo /> }) } finally { try { alive = false; await stop(); } finally { Script.exit() } } }
void main()